/*******************************************************************************
#                                                                              #
#      MJPG-streamer allows to stream JPG frames from an input-plugin          #
#      to several output plugins                                               #
#                                                                              #
#      Copyright (C) 2007 Tom Stöveken                                         #
#                                                                              #
# This program is free software; you can redistribute it and/or modify         #
# it under the terms of the GNU General Public License as published by         #
# the Free Software Foundation; version 2 of the License.                      #
#                                                                              #
# This program is distributed in the hope that it will be useful,              #
# but WITHOUT ANY WARRANTY; without even the implied warranty of               #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                #
# GNU General Public License for more details.                                 #
#                                                                              #
# You should have received a copy of the GNU General Public License            #
# along with this program; if not, write to the Free Software                  #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA    #
#                                                                              #
*******************************************************************************/

/*
  This output plugin is based on code from output_file.c
  Writen by Dimitrios Zachariadis
  Version 0.1, May 2010

  It provides a mechanism to take snapshots with a trigger from a UDP packet.
  The UDP msg contains the path for the snapshot jpeg file
  It echoes the message received back to the sender, after taking the snapshot
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <pthread.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>

#include <dirent.h>

#include "../../utils.h"
#include "../../mjpg_streamer.h"

#define OUTPUT_PLUGIN_NAME "UDP output plugin"

static pthread_t worker;
static globals *pglobal;
static int fd, delay, max_frame_size;
static char *folder = "/tmp";
static unsigned char *frame = NULL;
static char *command = NULL;
static int input_number = 0;

// UDP port
static int port = 8887;

/******************************************************************************
Description.: print a help message
Input Value.: -
Return Value: -
******************************************************************************/
void help(void)
{
    fprintf(stderr, " ---------------------------------------------------------------\n"
                    " 输出插件菜单...: " OUTPUT_PLUGIN_NAME "\n"
                    " ---------------------------------------------------------------\n"
                    " The following parameters can be passed to this plugin:\n\n"
                    " [-f | --folder ]........: folder to save pictures\n"
                    " [-d | --delay ].........: delay after saving pictures in ms\n"
                    " [-c | --command ].......: execute command after saveing picture\n"
                    " [-p | --port ]..........: UDP port to listen for picture requests. UDP message is the filename to save\n\n"
                    " [-i | --input ].......: read frames from the specified input plugin (first input plugin between the arguments is the 0th)\n\n"
                    " ---------------------------------------------------------------\n");
}

/******************************************************************************
Description.: clean up allocated resources
Input Value.: unused argument
Return Value: -
******************************************************************************/
void worker_cleanup(void *arg)
{
    static unsigned char first_run = 1;

    if (!first_run)
    {
        DBG("already cleaned up resources\n");
        return;
    }

    first_run = 0;
    OPRINT("cleaning up resources allocated by worker thread\n");

    if (frame != NULL)
    {
        free(frame);
    }
    close(fd);
}

/******************************************************************************
Description.: this is the main worker thread
              这是主工作线程
              it loops forever, grabs a fresh frame and stores it to file
              它会永远循环，抓取一个新的帧并将其存储到文件中
Input Value.:
Return Value:
******************************************************************************/

#if 0
// 线程函数，用于处理UDP消息并保存图片
void *worker_thread(void *arg)
{
    // frame_size记录了当前帧的大小
    int ok = 1, frame_size = 0, rc = 0;
    char buffer1[1024] = {0};
    unsigned char *tmp_framebuffer = NULL;

    /* set cleanup handler to cleanup allocated resources */
    // 设置线程清理处理程序,在线程终止时释放分配的资源。
    pthread_cleanup_push(worker_cleanup, NULL);

    // set UDP server data structures ---------------------------
    // 设置UDP服务器数据结构
    if (port <= 0)
    {
        OPRINT("必须提供一个有效的UDP端口\n");
        return NULL;
    }

    
    struct sockaddr_in addr;
    // 创建一个UDP套接字
    int sd;
    int bytes;
    unsigned int addr_len = sizeof(addr);
    char udpbuffer[1024] = {0};


    sd = socket(PF_INET, SOCK_DGRAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);


    if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
    {
        perror("bind绑定套接字失败");
    }
        
    // -----------------------------------------------------------

    while (ok >= 0 && !pglobal->stop)
    {
        DBG("等待UDP消息...\n");

        // UDP receive ---------------------------------------------
        memset(udpbuffer, 0, sizeof(udpbuffer));
        bytes = recvfrom(sd, udpbuffer, sizeof(udpbuffer), 0, (struct sockaddr *)&addr, &addr_len);
        // ---------------------------------------------------------

        DBG("等待新的视频帧传来\n");
        // 等待新的视频帧传来
        // 等待全局缓冲区更新
        pthread_mutex_lock(&pglobal->in[input_number].db);
        pthread_cond_wait(&pglobal->in[input_number].db_update, &pglobal->in[input_number].db);

        /* read buffer */
        // 读取全局缓冲区中的帧数据
        frame_size = pglobal->in[input_number].size;

        /* check if buffer for frame is large enough, increase it if necessary */
        // 检查帧数据的大小是否足够，必要时增加缓冲区大小
        if (frame_size > max_frame_size)
        {
            DBG("增加缓冲区大小到 %d\n", frame_size);

            max_frame_size = frame_size + (1 << 16);
            // 重新分配内存以适应新的帧大小
            if ((tmp_framebuffer = realloc(frame, max_frame_size)) == NULL)
            {
                pthread_mutex_unlock(&pglobal->in[input_number].db);
                LOG("内存空间不足\n");
                return NULL;
            }
            // 更新帧缓冲区指针
            frame = tmp_framebuffer;
        }

        /* copy frame to our local buffer now */
        // 将帧数据复制到本地缓冲区中
        memcpy(frame, pglobal->in[input_number].buf, frame_size);

        /* allow others to access the global buffer again */
        // 允许其他线程访问全局缓冲区
        pthread_mutex_unlock(&pglobal->in[input_number].db);

        /* only save a file if a name came in with the UDP message */
        // 如果UDP消息中包含文件名，则保存文件
        if (strlen(udpbuffer) > 0)
        {
            DBG("writing file: %s\n", udpbuffer);

            /* open file for write. Path must pre-exist */
            // 打开文件进行写入。路径必须预先存在
            if ((fd = open(udpbuffer, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0)
            {
                OPRINT("无法打开文件： %s\n", udpbuffer);
                return NULL;
            }

            /* save picture to file */
            // 将图片保存到文件中
            if (write(fd, frame, frame_size) < 0)
            {
                OPRINT("无法写入文件： %s\n", udpbuffer);
                perror("write()");
                close(fd);
                return NULL;
            }

            close(fd);
        }

        // send back client's message that came in udpbuffer
        // 将客户端的消息发送回udpbuffer
        sendto(sd, udpbuffer, bytes, 0, (struct sockaddr *)&addr, sizeof(addr));

        /* call the command if user specified one, pass current filename as argument */
        // 如果用户指定了命令，则调用命令，将当前文件名作为参数传递
        if (command != NULL)
        {
            // clear buffer1
            memset(buffer1, 0, sizeof(buffer1));

            /* udpbuffer still contains the filename, pass it to the command as parameter */
            // udpbuffer仍然包含文件名，将其传递给命令作为参数
            snprintf(buffer1, sizeof(buffer1), "%s \"%s\"", command, udpbuffer);
            DBG("calling command %s", buffer1);

            /* in addition provide the filename as environment variable */
            // 除了将文件名作为环境变量提供之外，还提供环境变量
            if ((rc = setenv("MJPG_FILE", udpbuffer, 1)) != 0)
            {
                LOG("setenv failed (return value %d)\n", rc);
            }

            /* execute the command now */
            // 现在执行命令
            if ((rc = system(buffer1)) != 0)
            {
                LOG("command failed (return value %d)\n", rc);
            }
        }

        /* if specified, wait now */
        // 如果指定了延迟，则等待
        if (delay > 0)
        {
            usleep(1000 * delay);
        }
    }

    // close UDP port
    // 关闭UDP端口
    if (port > 0)
    {
        close(sd);
    }
        

    /* cleanup now */
    // 现在清理
    pthread_cleanup_pop(1);

    return NULL;
}

#else

void *worker_thread(void *arg)
{
    // frame_size记录了当前帧的大小
    int ok = 1, frame_size = 0, rc = 0;

    char buffer1[1024] = {0};

    // 分配内存用于存储帧数据
    unsigned char *tmp_framebuffer = NULL;

    /* set cleanup handler to cleanup allocated resources */
    // 设置线程清理处理程序,在线程终止时释放分配的资源。
    pthread_cleanup_push(worker_cleanup, NULL);

    // 接收到的消息的长度
    int bytes;

    char udpbuffer[1024] = {0};

    // set UDP server data structures ---------------------------
    // 设置UDP服务器数据结构
    if (port <= 0)
    {
        OPRINT("必须提供一个有效的UDP端口\n");
        return NULL;
    }

    // 1.创建数据报套接字
    int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socketfd < 0)
    {
        perror("UDP套接字创建失败");
        return NULL;
    }
    printf("UDP套接字创建成功\n");
    printf("socketfd = %d\n", socketfd);

    // 2.绑定信息
    struct sockaddr_in serveraddr, qtaddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(port);
    serveraddr.sin_addr.s_addr = INADDR_ANY;
    unsigned int qtaddr_len = sizeof(qtaddr);
    bzero(&qtaddr, sizeof(qtaddr));

    // 给该进程的套接字绑定地址和端口
    if (bind(socketfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) != 0)
    {
        perror("bind绑定套接字失败");
    }
    printf("UDP套接字绑定成功\n");
    // -----------------------------------------------------------
    printf("\n等待UDP消息...\n");

    while (1)
    {
        // UDP receive ---------------------------------------------
        memset(udpbuffer, 0, sizeof(udpbuffer));
        // 将UDP消息从套接字中接收，并将消息长度保存在bytes中
        bytes = recvfrom(socketfd, udpbuffer, sizeof(udpbuffer), 0, (struct sockaddr *)&qtaddr, &qtaddr_len);
        // ---------------------------------------------------------
        if (!strcmp(udpbuffer, "get_stream"))
        {
            printf("接收到来自QT客户端的消息: %s\n", udpbuffer);
            break;
        }
    }
    int num = 1;
    while (ok >= 0 && !pglobal->stop)
    {
        
        delay = 16.67;
        DBG("等待UDP消息...\n");
        // // UDP receive ---------------------------------------------
        // memset(udpbuffer, 0, sizeof(udpbuffer));
        // // 将UDP消息从套接字中接收，并将消息长度保存在bytes中
        // bytes = recvfrom(socketfd, udpbuffer, sizeof(udpbuffer), 0, (struct sockaddr *)&qtaddr, &qtaddr_len);
        // ---------------------------------------------------------

        DBG("等待新的视频帧传来\n", num++);
        printf("等待新的视频帧传来%d\n", num++);
        // 等待新的视频帧传来
        // 等待全局缓冲区更新
        pthread_mutex_lock(&pglobal->in[input_number].db);
        pthread_cond_wait(&pglobal->in[input_number].db_update, &pglobal->in[input_number].db);

        /* read buffer */
        // 获取帧数据的大小
        frame_size = pglobal->in[input_number].size;
        // 检查帧数据的大小是否足够，必要时增加缓冲区大小
        if (frame_size > max_frame_size)
        {
            DBG("增加缓冲区大小到 %d\n", frame_size);
            max_frame_size = frame_size + (1 << 16);
            // 重新分配内存以适应新的帧大小
            if ((tmp_framebuffer = realloc(frame, max_frame_size)) == NULL)
            {
                pthread_mutex_unlock(&pglobal->in[input_number].db);
                LOG("内存空间不足\n");
                return NULL;
            }
            // 更新帧缓冲区指针
            frame = tmp_framebuffer;
        }

        /* copy frame to our local buffer now */
        // 将全局缓冲区中的帧数据复制到本地缓冲区中
        memcpy(frame, pglobal->in[input_number].buf, frame_size);

        /* allow others to access the global buffer again */
        // 允许其他线程访问全局缓冲区
        pthread_mutex_unlock(&pglobal->in[input_number].db);

        if (!strcmp(udpbuffer, "get_stream"))
        {
            // 发送帧数据到客户端
            if (sendto(socketfd, frame, frame_size, 0, (struct sockaddr *)&qtaddr, sizeof(qtaddr)) < 0)
            {
                OPRINT("无法发送帧数据到客户端\n");
                perror("sendto()");
                return NULL;
            }
            printf("发送视频帧成功\n");
        }

        /* only save a file if a name came in with the UDP message */
        // 如果UDP消息中包含文件名，则保存文件
        // if (strlen(udpbuffer) > 0)
        // {
        //     DBG("writing file: %s\n", udpbuffer);

        //     /* open file for write. Path must pre-exist */
        //     // 打开文件进行写入。路径必须预先存在
        //     if ((fd = open(udpbuffer, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0)
        //     {
        //         OPRINT("无法打开文件： %s\n", udpbuffer);
        //         return NULL;
        //     }

        //     /* save picture to file */
        //     // 将图片保存到文件中
        //     if (write(fd, frame, frame_size) < 0)
        //     {
        //         OPRINT("无法写入文件： %s\n", udpbuffer);
        //         perror("write()");
        //         close(fd);
        //         return NULL;
        //     }

        //     close(fd);
        // }

        // send back client's message that came in udpbuffer
        // 将客户端的消息发送回udpbuffer
        // sendto(socketfd, udpbuffer, bytes, 0, (struct sockaddr *)&qtaddr, sizeof(qtaddr));

        /* call the command if user specified one, pass current filename as argument */
        // 如果用户指定了命令，则调用命令，将当前文件名作为参数传递
        // if (command != NULL)
        // {
        //     // clear buffer1
        //     memset(buffer1, 0, sizeof(buffer1));

        //     /* udpbuffer still contains the filename, pass it to the command as parameter */
        //     // udpbuffer仍然包含文件名，将其传递给命令作为参数
        //     snprintf(buffer1, sizeof(buffer1), "%s \"%s\"", command, udpbuffer);
        //     DBG("calling command %s", buffer1);

        //     /* in addition provide the filename as environment variable */
        //     // 除了将文件名作为环境变量提供之外，还提供环境变量
        //     if ((rc = setenv("MJPG_FILE", udpbuffer, 1)) != 0)
        //     {
        //         LOG("setenv failed (return value %d)\n", rc);
        //     }

        //     /* execute the command now */
        //     // 现在执行命令
        //     if ((rc = system(buffer1)) != 0)
        //     {
        //         LOG("command failed (return value %d)\n", rc);
        //     }
        // }

        /* if specified, wait now */
        // 如果指定了延迟，则等待
        if (delay > 0)
        {
            usleep(1000 * delay);
        }
    }

    // close UDP port
    // 关闭UDP端口
    if (port > 0)
    {
        close(socketfd);
    }

    /* cleanup now */
    // 现在清理
    pthread_cleanup_pop(1);

    return NULL;
}

#endif

/*** plugin interface functions ***/
/******************************************************************************
Description.: this function is called first, in order to initialise
              this plugin and pass a parameter string
Input Value.: parameters
Return Value: 0 if everything is ok, non-zero otherwise
******************************************************************************/
int output_init(output_parameter *param)
{
    int i;

    delay = 0;

    param->argv[0] = OUTPUT_PLUGIN_NAME;

    /* show all parameters for DBG purposes */
    for (i = 0; i < param->argc; i++)
    {
        DBG("argv[%d]=%s\n", i, param->argv[i]);
    }

    reset_getopt();
    while (1)
    {
        int option_index = 0, c = 0;
        static struct option long_options[] = {
            {"h", no_argument, 0, 0},
            {"help", no_argument, 0, 0},
            {"f", required_argument, 0, 0},
            {"folder", required_argument, 0, 0},
            {"d", required_argument, 0, 0},
            {"delay", required_argument, 0, 0},
            {"c", required_argument, 0, 0},
            {"command", required_argument, 0, 0},
            {"p", required_argument, 0, 0},
            {"port", required_argument, 0, 0},
            {"i", required_argument, 0, 0},
            {"input", required_argument, 0, 0},
            {0, 0, 0, 0}};

        c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index);

        /* no more options to parse */
        if (c == -1)
            break;

        /* unrecognized option */
        if (c == '?')
        {
            help();
            return 1;
        }

        switch (option_index)
        {
            /* h, help */
        case 0:
        case 1:
            DBG("case 0,1\n");
            help();
            return 1;
            break;

            /* f, folder */
        case 2:
        case 3:
            DBG("case 2,3\n");
            folder = malloc(strlen(optarg) + 1);
            strcpy(folder, optarg);
            if (folder[strlen(folder) - 1] == '/')
                folder[strlen(folder) - 1] = '\0';
            break;

            /* d, delay */
        case 4:
        case 5:
            DBG("case 4,5\n");
            delay = atoi(optarg);
            break;

            /* c, command */
        case 6:
        case 7:
            DBG("case 6,7\n");
            command = strdup(optarg);
            break;
            /* p, port */
        case 8:
        case 9:
            DBG("case 8,9\n");
            port = atoi(optarg);
            break;
            /* i, input */
        case 10:
        case 11:
            DBG("case 10,11\n");
            input_number = atoi(optarg);
            break;
        }
    }

    pglobal = param->global;
    if (!(input_number < pglobal->incnt))
    {
        OPRINT("ERROR: the %d input_plugin number is too much only %d plugins loaded\n", input_number, pglobal->incnt);
        return 1;
    }
    OPRINT("input plugin.....: %d: %s\n", input_number, pglobal->in[input_number].plugin);
    OPRINT("output folder.....: %s\n", folder);
    OPRINT("delay after save..: %d\n", delay);
    OPRINT("command...........: %s\n", (command == NULL) ? "disabled" : command);
    if (port > 0)
    {
        OPRINT("UDP port..........: %d\n", port);
    }
    else
    {
        OPRINT("UDP port..........: %s\n", "disabled");
    }
    return 0;
}

/******************************************************************************
Description.: calling this function stops the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_stop(int id)
{
    DBG("will cancel worker thread\n");
    pthread_cancel(worker);
    return 0;
}

/******************************************************************************
Description.: calling this function creates and starts the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_run(int id)
{
    DBG("launching worker thread\n");
    pthread_create(&worker, 0, worker_thread, NULL);
    pthread_detach(worker);
    return 0;
}
